using System;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Threading;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.transaction
{

	using Test = junit.framework.Test;
	using TestCase = junit.framework.TestCase;
	using TestSuite = junit.framework.TestSuite;

	using DeadlockDetectedException = org.neo4j.kernel.impl.transaction.DeadlockDetectedException;
	using LockManager = org.neo4j.kernel.impl.transaction.LockManager;

	public class TestRWLock : TestCase
	{
		private LockManager lm = new LockManager(new PlaceboTm());

		public TestRWLock(string testName) : base(testName)
		{
		}

		static void Main(string[] args)
		{
			junit.textui.TestRunner.run(suite());
		}

		public static Test suite()
		{
			TestSuite suite = new TestSuite(typeof(TestRWLock));
			return suite;
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public void testSingleThread() throws Exception
		public virtual void testSingleThread()
		{
			try
			{
				lm.getReadLock(null);
				fail("Null parameter should throw exception");
			}
			catch (Exception e)
			{
			// good
			}
			try
			{
				lm.getWriteLock(null);
				fail("Null parameter should throw exception");
			}
			catch (Exception e)
			{
			// good
			}
			try
			{
				lm.releaseReadLock(null);
				fail("Null parameter should throw exception");
			}
			catch (Exception e)
			{
			// good
			}
			try
			{
				lm.releaseWriteLock(null);
				fail("Null parameter should throw exception");
			}
			catch (Exception e)
			{
			// good
			}

			object entity = new object();
			try
			{
				lm.releaseWriteLock(entity);
				fail("Invalid release should throw exception");
			}
			catch (Exception e)
			{
			// good
			}
			try
			{
				lm.releaseReadLock(entity);
				fail("Invalid release should throw exception");
			}
			catch (Exception e)
			{
			// good
			}

			lm.getReadLock(entity);
			try
			{
				lm.releaseWriteLock(entity);
				fail("Invalid release should throw exception");
			}
			catch (Exception e)
			{
			// good
			}
			lm.releaseReadLock(entity);
			lm.getWriteLock(entity);
			try
			{
				lm.releaseReadLock(entity);
				fail("Invalid release should throw exception");
			}
			catch (Exception e)
			{
			// good
			}
			lm.releaseWriteLock(entity);

			lm.getReadLock(entity);
			lm.getWriteLock(entity);
			lm.releaseWriteLock(entity);
			lm.releaseReadLock(entity);

			lm.getWriteLock(entity);
			lm.getReadLock(entity);
			lm.releaseReadLock(entity);
			lm.releaseWriteLock(entity);

			for (int i = 0; i < 10; i++)
			{
				if ((i % 2) == 0)
				{
					lm.getWriteLock(entity);
				}
				else
				{
					lm.getReadLock(entity);
				}
			}
			for (int i = 9; i >= 0; i--)
			{
				if ((i % 2) == 0)
				{
					lm.releaseWriteLock(entity);
				}
				else
				{
					lm.releaseReadLock(entity);
				}
			}
		}

		private class HelperThread : Thread
		{
			private const long MAX_WAIT_LOOPS = 40;

			private const int DO_NOTHING_TASK = 0;
			private const int GET_READLOCK_TASK = 1;
			private const int GET_WRITELOCK_TASK = 2;
			private const int RELEASE_READLOCK_TASK = 3;
			private const int RELEASE_WRITELOCK_TASK = 4;
			private const int QUIT_TASK = 5;

			private string name = null;
			private int nextTask = 0;
			private bool taskCompleted = true;
			private object resource = null;

			internal HelperThread(string name) : base()
			{
				this.name = name;
			}

			public virtual void run()
			{
				try
				{
					while (nextTask != QUIT_TASK)
					{
						switch (nextTask)
						{
							case DO_NOTHING_TASK:
								lock (this)
								{
									wait(15);
								}
								break;
							case GET_READLOCK_TASK:
								lm.getReadLock(resource);
								taskCompleted = true;
								nextTask = DO_NOTHING_TASK;
								break;
							case RELEASE_READLOCK_TASK:
								lm.releaseReadLock(resource);
								taskCompleted = true;
								nextTask = DO_NOTHING_TASK;
								break;
							case GET_WRITELOCK_TASK:
								lm.getWriteLock(resource);
								taskCompleted = true;
								nextTask = DO_NOTHING_TASK;
								break;
							case RELEASE_WRITELOCK_TASK:
								lm.releaseWriteLock(resource);
								taskCompleted = true;
								nextTask = DO_NOTHING_TASK;
								break;
							default:
								throw new RuntimeException("Argh");
						}
					}
				}
				catch (Exception e)
				{
					taskCompleted = true;
					Console.WriteLine("" + this + " unable to execute task, " + e);
					e.printStackTrace();
					throw new RuntimeException(e);
				}
			}

			[MethodImpl(MethodImplOptions.Synchronized)]
			internal virtual void waitForCompletionOfTask()
			{
				int count = 0;
				while (!taskCompleted)
				{
					if (count > MAX_WAIT_LOOPS)
					{
						throw new RuntimeException("Task timed out");
					}

					try
					{
						wait(25);
					}
					catch (InterruptedException e)
					{
					}
					count++;
				}
			}

			internal virtual bool isTaskCompleted()
			{
				return taskCompleted;
			}

			[MethodImpl(MethodImplOptions.Synchronized)]
			internal virtual void getReadLock(object resource)
			{
				if (!taskCompleted)
				{
					throw new RuntimeException("Task not completed");
				}
				taskCompleted = false;
				this.resource = resource;
				nextTask = GET_READLOCK_TASK;
			}

			[MethodImpl(MethodImplOptions.Synchronized)]
			internal virtual void releaseReadLock(object resource)
			{
				if (!taskCompleted)
				{
					throw new RuntimeException("Task not completed");
				}
				taskCompleted = false;
				this.resource = resource;
				nextTask = RELEASE_READLOCK_TASK;
			}

			[MethodImpl(MethodImplOptions.Synchronized)]
			internal virtual void getWriteLock(object resource)
			{
				if (!taskCompleted)
				{
					throw new RuntimeException("Task not completed");
				}
				taskCompleted = false;
				this.resource = resource;
				nextTask = GET_WRITELOCK_TASK;
			}

			[MethodImpl(MethodImplOptions.Synchronized)]
			internal virtual void releaseWriteLock(object resource)
			{
				if (!taskCompleted)
				{
					throw new RuntimeException("Task not completed");
				}
				taskCompleted = false;
				this.resource = resource;
				nextTask = RELEASE_WRITELOCK_TASK;
			}

			internal virtual void quit()
			{
				taskCompleted = false;
				nextTask = QUIT_TASK;
			}

			public override string ToString()
			{
				return name;
			}
		}

		private class ResourceObject
		{
			private string name = null;

			internal ResourceObject(string name)
			{
				this.name = name;
			}

			public override string ToString()
			{
				return this.name;
			}
		}

		public virtual void testMultipleThreads()
		{
			HelperThread t1 = new HelperThread("T1");
			HelperThread t2 = new HelperThread("T2");
			HelperThread t3 = new HelperThread("T3");
			HelperThread t4 = new HelperThread("T4");
			ResourceObject r1 = new ResourceObject("R1");
			try
			{
				t1.Start();
				t2.Start();
				t3.Start();
				t4.Start();

				t1.getReadLock(r1);
				t1.waitForCompletionOfTask();
				t2.getReadLock(r1);
				t2.waitForCompletionOfTask();
				t3.getReadLock(r1);
				t3.waitForCompletionOfTask();
				t4.getWriteLock(r1);
				t3.releaseReadLock(r1);
				t3.waitForCompletionOfTask();
				t2.releaseReadLock(r1);
				t2.waitForCompletionOfTask();
				assertTrue(!t4.isTaskCompleted());
				t1.releaseReadLock(r1);
				t1.waitForCompletionOfTask();
			// now we can wait for write lock since it can be acquired
			// get write lock
				t4.waitForCompletionOfTask();
				t4.getReadLock(r1);
				t4.waitForCompletionOfTask();
				t4.getReadLock(r1);
				t4.waitForCompletionOfTask();
			// put readlock in queue
				t1.getReadLock(r1);
				t4.getReadLock(r1);
				t4.waitForCompletionOfTask();
				t4.releaseReadLock(r1);
				t4.waitForCompletionOfTask();
				t4.getWriteLock(r1);
				t4.waitForCompletionOfTask();
				t4.releaseWriteLock(r1);
				t4.waitForCompletionOfTask();
				assertTrue(!t1.isTaskCompleted());
				t4.releaseWriteLock(r1);
				t4.waitForCompletionOfTask();
			// get read lock
				t1.waitForCompletionOfTask();
				t4.releaseReadLock(r1);
				t4.waitForCompletionOfTask();
			// t4 now has 1 readlock and t1 one readlock
			// let t1 drop readlock and t4 get write lock
				t4.getWriteLock(r1);
				t1.releaseReadLock(r1);
				t1.waitForCompletionOfTask();
				t4.waitForCompletionOfTask();

				t4.releaseReadLock(r1);
				t4.waitForCompletionOfTask();
				t4.releaseWriteLock(r1);
				t4.waitForCompletionOfTask();

				t4.getWriteLock(r1);
				t4.waitForCompletionOfTask();
				t1.getReadLock(r1);
				t2.getReadLock(r1);
				t3.getReadLock(r1);
				t4.getReadLock(r1);
				t4.waitForCompletionOfTask();
				t4.releaseWriteLock(r1);
				t4.waitForCompletionOfTask();
				t1.waitForCompletionOfTask();
				t2.waitForCompletionOfTask();
				t3.waitForCompletionOfTask();

				t1.getWriteLock(r1);
				t2.releaseReadLock(r1);
				t2.waitForCompletionOfTask();
				t4.releaseReadLock(r1);
				t4.waitForCompletionOfTask();
				t3.releaseReadLock(r1);
				t3.waitForCompletionOfTask();

				t1.waitForCompletionOfTask();
				t1.releaseWriteLock(r1);
				t1.waitForCompletionOfTask();
				t2.getReadLock(r1);
				t2.waitForCompletionOfTask();
				t1.releaseReadLock(r1);
				t1.waitForCompletionOfTask();
				t2.getWriteLock(r1);
				t2.waitForCompletionOfTask();
				t2.releaseWriteLock(r1);
				t2.waitForCompletionOfTask();
				t2.releaseReadLock(r1);
				t2.waitForCompletionOfTask();
			}
			catch (Exception e)
			{
				lm.dumpLocksOnResource(r1);
				e.printStackTrace();
				fail("Multiple thread rw lock test failed, " + e);
			}
			finally
			{
				t1.quit();
				t2.quit();
				t3.quit();
				t4.quit();
			}
		}

		private static bool go = false;

		public class StressThread : Thread
		{
			private System.Random rand = new System.Random(System.currentTimeMillis());
			private readonly object READ = new object();
			private readonly object WRITE = new object();

			private string name;
			private int numberOfIterations;
			private int depthCount;
			private float readWriteRatio;
			private object resource;

			internal StressThread(string name, int numberOfIterations, int depthCount, float readWriteRatio, object resource) : base()
			{
				this.name = name;
				this.numberOfIterations = numberOfIterations;
				this.depthCount = depthCount;
				this.readWriteRatio = readWriteRatio;
				this.resource = resource;
			}

			public virtual void run()
			{
				try
				{
					while (!go)
					{
						try
						{
							sleep(100);
						}
						catch (InterruptedException e)
						{
						}
					}
					Stack<object> lockStack = new Stack<object>();
					try
					{
						for (int i = 0; i < numberOfIterations; i++)
						{
							int depth = depthCount;
							do
							{
								float f = rand.nextFloat();
								if (f < readWriteRatio)
								{
									lm.getReadLock(resource);
									lockStack.Push(READ);
								}
								else
								{
									lm.getWriteLock(resource);
									lockStack.Push(WRITE);
								}
							}
							while (--depth > 0);

							while (!lockStack.isEmpty())
							{
								if (lockStack.Pop() == READ)
								{
									lm.releaseReadLock(resource);
								}
								else
								{
									lm.releaseWriteLock(resource);
								}
							}
						}
					}
					catch (DeadlockDetectedException e)
					{
					// System.out.println( "#############Deadlock detected!" );
					}
					finally
					{
						while (!lockStack.isEmpty())
						{
							if (lockStack.Pop() == READ)
							{
								lm.releaseReadLock(resource);
							}
							else
							{
								lm.releaseWriteLock(resource);
							}
						}
					}
				}
				catch (Exception e)
				{
					e.printStackTrace();
					throw new RuntimeException(e);
				}
			}

			public override string ToString()
			{
				return this.name;
			}
		}

		public virtual void testStressMultipleThreads()
		{
			ResourceObject r1 = new ResourceObject("R1");
			Thread[] stressThreads = new Thread[100];
			go = false;
			for (int i = 0; i < 100; i++)
			{
				stressThreads[i] = new StressThread("Thread" + i, 100, 9, 0.50f, r1);
			}
			for (int i = 0; i < 100; i++)
			{
				stressThreads[i].Start();
			}
			go = true;
		}
	}
}